import random, sys, pygame, time
from pygame.locals import *

FPS = 25
WINDOWWIDTH = 520
WINDOWHEIGHT = 620
BOXSIZE = 20
BOARDWIDTH = 10
BOARDHEIGHT = 30

XMARGIN = (WINDOWWIDTH - BOARDWIDTH * BOXSIZE) // 2
TOPMARGIN = WINDOWHEIGHT - (BOXSIZE * BOARDHEIGHT) - 8

BLANK = '.'
MOVESIDEWAYSFREQ = 0.15
MOVEDOWNFREQ = 0.1

#               R    G    B
WHITE       = (255, 255, 255)
GRAY        = (185, 185, 185)
BLACK       = (   0,  0,   0)
RED         = (155,   0,   0)
LIGHTRED    = (175,  20,  20)
GREEN       = (  0, 155,   0)
LIGHTGREEN  = ( 20, 175,  20)
BLUE        = (  0,   0, 175)
LIGHTBLUE   = ( 20,  20, 175)
YELLOW      = (155, 155,   0)
LIGHTYELLOW = (175, 175,  20)

BORDERCOLOR = BLUE
BGCOLOR = BLACK
TEXTCOLOR = WHITE
TEXTSHADOWCOLOR = GRAY
COLORS = (BLUE, GREEN, RED, YELLOW)
LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW)
assert len(COLORS) == len(LIGHTCOLORS), '每个颜色都有对应的轻色'

TEMPLATEWIDTH = 5
TEMPLATEHEIGHT = 5
S_SHAPE_TEMPLATE = [['.....',
                     '.....',
                     '..00.',
                     '.00..',
                     '.....'],
                    ['.....',
                     '..0..',
                     '..00.',
                     '...0.',
                     '.....']]
Z_SHAPE_TEMPLATE = [['.....',
                     '.....',
                     '.00..',
                     '..00.',
                     '.....'],
                    ['.....',
                     '..0..',
                     '.00..',
                     '.0...',
                     '.....']]
I_SHAPE_TEMPLATE = [['..0..',
                     '..0..',
                     '..0..',
                     '..0..',
                     '.....'],
                    ['.....',
                     '.....',
                     '0000.',
                     '.....',
                     '.....']]
O_SHAPE_TEMPLATE = [['.....',
                     '.....',
                     '.00..',
                     '.00..',
                     '.....']]
J_SHAPE_TEMPLATE = [['.....',
                     '..0..',
                     '..0..',
                     '.00..',
                     '.....'],
                    ['.....',
                     '.0...',
                     '.000.',
                     '.....',
                     '.....'],
                    ['.....',
                     '..00.',
                     '..0..',
                     '..0..',
                     '.....'],
                    ['.....',
                     '.....',
                     '.000.',
                     '...0.',
                     '.....']]
L_SHAPE_TEMPLATE = [['.....',
                     '..0..',
                     '..0..',
                     '..00.',
                     '.....'],
                    ['.....',
                     '.....',
                     '.000.',
                     '.0...',
                     '.....'],
                    ['.....',
                     '.00..',
                     '..0..',
                     '..0..',
                     '.....'],
                    ['.....',
                     '...0.',
                     '.000.',
                     '.....',
                     '.....']]
SHAPES = {'S': S_SHAPE_TEMPLATE,
          'Z': Z_SHAPE_TEMPLATE,
          'I': I_SHAPE_TEMPLATE,
          'O': O_SHAPE_TEMPLATE,
          'J': J_SHAPE_TEMPLATE,
          'L': L_SHAPE_TEMPLATE}
def getBlankBoard():
    board = []
    for i in range(BOARDWIDTH):
        board.append([BLANK] * BOARDHEIGHT)
    return board
def drawBoard(board):
    pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN-3, TOPMARGIN-7, (BOARDWIDTH*BOXSIZE)+8, (BOARDHEIGHT*BOXSIZE)+8), 5)
    pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN, (BOARDWIDTH*BOXSIZE), (BOARDHEIGHT*BOXSIZE)))
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            drawBox(x, y, board[x][y])
def drawBox(boxx, boxy, color, pixelx=None, pixely=None):
    if color == BLANK:
        return
    if pixelx == None and pixely == None:
        pixelx, pixely = convertToPixelCoords(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, COLORS[color], (pixelx+1, pixely+1, BOXSIZE-1, BOXSIZE-1))
    pygame.draw.rect(DISPLAYSURF, LIGHTCOLORS[color], (pixelx+1, pixely+1, BOXSIZE-4, BOXSIZE-4))
def drawStatus(score, level):
    scoreSurf = BASICFONT.render('Score：%s'%score, True, TEXTCOLOR)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (WINDOWWIDTH-150, 20)
    levelSurf = BASICFONT.render('Level：%s'%level, True, TEXTCOLOR)
    levelRect = levelSurf.get_rect()
    levelRect.topleft = (WINDOWWIDTH-150, 50)
    
    DISPLAYSURF.blit(scoreSurf, scoreRect)
    DISPLAYSURF.blit(levelSurf, levelRect)
def calculateLevelAndFallFreq(score):
    level = score//10 + 1
    fallFreq = 0.27 - (level*0.02)
    return level, fallFreq
def drawNextPiece(piece):
    nextSurf = BASICFONT.render('Next：', True, TEXTCOLOR)
    nextRect = nextSurf.get_rect()
    nextRect.topleft = (WINDOWWIDTH-120, 80)
    DISPLAYSURF.blit(nextSurf, nextRect)
    drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=100)
def convertToPixelCoords(boxx, boxy):
    return XMARGIN+boxx*BOXSIZE, TOPMARGIN+boxy*BOXSIZE
def drawPiece(piece, pixelx=None, pixely=None):
    shapeToDraw = SHAPES[piece['shape']][piece['rotation']]
    if pixelx == None and pixely == None:
        pixelx, pixely = convertToPixelCoords(piece['x'], piece['y'])
    for x in range(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            if shapeToDraw[y][x] != BLANK:
                drawBox(None, None, piece['color'], pixelx+(x*BOXSIZE), pixely+(y*BOXSIZE))
def getNewPiece():
    shape = random.choice(list(SHAPES.keys()))
    newPiece = {'shape':shape,
                'rotation':random.randint(0, len(SHAPES[shape]) - 1),
                'x':BOARDWIDTH//2 - TEMPLATEWIDTH//2,
                'y':-2,
                'color':random.randint(0, len(COLORS)-1)}
    return newPiece
def runGame():
    board = getBlankBoard()
    lastFallTime = time.time()
    lastMoveDownTime = time.time()
    lastMoveSidewaysTime = time.time()
    movingLeft = False
    movingRight = False
    movingDown = False    
    score = 0
    level, fallFreq = calculateLevelAndFallFreq(score)
    fallingPiece = getNewPiece()
    nextPiece = getNewPiece()
    while True:
        if fallingPiece == None:
            fallingPiece = nextPiece
            nextPiece = getNewPiece()
            lastFallTime = time.time()
            
            if not isValidPosition(board, fallingPiece):
                return #容不下新的方块，游戏结束
            
        checkForQuit()
        for event in pygame.event.get():
            if event.type == KEYUP:
                if (event.key == K_p):
                    DISPLAYSURF.fill(BGCOLOR)
                    pygame.mixer.music.stop()
                    showTextScreen('Paused')
                    pygame.mixer.music.play(-1, 0.0)
                    lastFallTime = time.time()
                    lastMoveDownTime = time.time()
                    lastMoveSidewayTime = time.time()
                elif (event.key == K_LEFT or event.key == K_a):
                    movingLeft = False
                elif (event.key == K_RIGHT or event.key == K_d):
                    movingRight = False
                elif (event.key == K_DOWN or event.key == K_s):
                    movingDown = False
            elif event.type == KEYDOWN:
                if (event.key == K_UP or event.key == K_w):
                    fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(SHAPES[fallingPiece['shape']])
                    if not isValidPosition(board, fallingPiece): #没有空间余地可旋转，恢复原状
                        fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(SHAPES[fallingPiece['shape']])
                elif (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1):
                    fallingPiece['x'] -= 1
                    movingLeft = True
                    movingRight = False
                    lastMoveSidewaysTime = time.time()
                elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1):
                    fallingPiece['x'] += 1
                    movingLeft = False
                    movingRight = True
                    lastMoveSidewaysTime = time.time()
                elif (event.key == K_DOWN or event.key == K_s):
                    movingDown = True
                    if isValidPosition(board, fallingPiece, adjY=1):
                        fallingPiece['y'] += 1
                    lastMoveDownTime = time.time()
                elif event.key == K_SPACE:
                    movingLeft = False
                    movingRight = False
                    movingDown = False
                    for i in range(1, BOARDHEIGHT):
                        if not isValidPosition(board, fallingPiece, adjY=i):
                            break
                    fallingPiece['y'] += i - 1
        if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ:
            if movingLeft and isValidPosition(board, fallingPiece, adjX=-1):
                fallingPiece['x'] -= 1
            elif movingRight and isValidPosition(board, fallingPiece, adjX=1):
                fallingPiece['x'] += 1
            lastMoveSidewaysTime = time.time()
    
        if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1):
            fallingPiece['y'] += 1
            lastMoveDownTime = time.time()
        if time.time() - lastFallTime > fallFreq:
            if isValidPosition(board, fallingPiece, adjY=1):
                fallingPiece['y'] += 1
                lastFallTime = time.time()
            else:
                addToBoard(board, fallingPiece)
                score += removeCompleteLines(board)
                level, fallFreq = calculateLevelAndFallFreq(score)
                fallingPiece = None
        DISPLAYSURF.fill(BGCOLOR)
        drawBoard(board)
        drawStatus(score, level)
        drawNextPiece(nextPiece)
        if fallingPiece != None:
            drawPiece(fallingPiece)
        pygame.display.update()
        FPSCLOCK.tick(FPS)
def removeCompleteLines(board):
    numLinesRemoved = 0
    y = BOARDHEIGHT - 1
    while y >= 0:
        if isCompleteLine(board, y):
            for pullDownY in range(y, 0, -1):
                for x in range(BOARDWIDTH):
                    board[x][pullDownY] = board[x][pullDownY - 1]
                for x in range(BOARDWIDTH):
                    board[x][0] = BLANK
            numLinesRemoved += 1
        else:
            y -= 1
    return numLinesRemoved
def isCompleteLine(board, y):
    for x in range(BOARDWIDTH):
        if board[x][y] == BLANK:
            return False
    return True
def addToBoard(board, piece):
    for x in range(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            if SHAPES[piece['shape']][piece['rotation']][y][x] != BLANK:
                board[piece['x']+x][piece['y']+y] = piece['color']
def isValidPosition(board, piece, adjX=0, adjY=0):
    for x in range(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            isAboveBoard = (piece['y'] + y + adjY < 0)
            if isAboveBoard or SHAPES[piece['shape']][piece['rotation']][y][x] == BLANK:
                continue
            if not isOnBoard(piece['x']+x+adjX, piece['y']+y+adjY):
                return False
            elif board[piece['x']+x+adjX][piece['y']+y+adjY] != BLANK: #发生碰撞
                return False
    return True
def isOnBoard(x, y):
    return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT
def makeTextObjs(text, font, color):
    surf = font.render(text, True, color)
    return surf, surf.get_rect()
def showTextScreen(text):
    titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR)
    titleRect.center = (WINDOWWIDTH//2, WINDOWHEIGHT//2)
    DISPLAYSURF.blit(titleSurf, titleRect)
    titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR)
    titleRect.center = (WINDOWWIDTH//2-3, WINDOWHEIGHT//2-3)
    DISPLAYSURF.blit(titleSurf, titleRect)
    
    pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play', BASICFONT, TEXTCOLOR)
    pressKeyRect.center = (WINDOWWIDTH//2, WINDOWHEIGHT//2+100)
    DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
    
    while checkForKeyPress() == None:
        pygame.display.update()
        FPSCLOCK.tick()
def terminate():
    pygame.quit()
    sys.exit()
def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event)
def checkForKeyPress():
    checkForQuit()
    for event in pygame.event.get([KEYDOWN, KEYUP]):
        if event.type == KEYDOWN:
            continue
        return event.key
    return None


def tetromino():
    global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    BASICFONT = pygame.font.Font('res/msyh.ttc', 18)
    BIGFONT = pygame.font.Font('res/msyh.ttc', 90)
    pygame.display.set_caption('俄罗斯方块')

    #     showTextScreen('Tetromino')
    while True:
        if random.randint(0, 1) == 0:
            pygame.mixer.music.load('res/tetrisb.mp3')
        else:
            pygame.mixer.music.load('res/tetrisc.mp3')
        pygame.mixer.music.play(-1, 0.0)
        runGame()
        pygame.mixer.music.stop()
        showTextScreen('Game Over')
if __name__ == '__main__':
    tetromino()